package version1

import (
	"container/list"
	"encoding/json"
	"errors"
	"net/http"
	"server/global"
	"server/middleware"
	"server/model"
	"server/myutils"
	"sync"

	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"gorm.io/gorm"
)

//存储用户数据--用sync.map[防止并发锁]
var (
	LoginMap = sync.Map{}
)

var (
	Upgrader = websocket.Upgrader{
		//允许跨域
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}
)

//@Summary ws服务升级
//@Router /ws [get]
//@Tags WS
//@Description 升级websocket服务,获取订阅信息
//@Produce json
//@Accept json
//@Success 200 {object} model.StandardResponse{} `接口被正确调用,但是调用结果看status状态码`
func WS(req *gin.Context) {
	var claim = new(model.Chathandle)
	conn, _ := Upgrader.Upgrade(req.Writer, req.Request, nil)
	for {
		//服务升级
		_, data, err := conn.ReadMessage()
		if err != nil {
			//以Email作为key值
			removeClientIn(claim.Email, conn)
			return
		}
		//数据token验证+登录验证
		json.Unmarshal(data, claim)
		token, err2 := loginortoken(claim)
		if err2 != nil {
			//发送错误信息-->单体发送  ->用json.Marshal(map)发送json数据
			b, _ := json.Marshal(gin.H{
				"type":    "error",
				"context": err2.Error(),
			})
			conn.WriteMessage(websocket.TextMessage, b)
			continue
		}
		if token != claim.Auth {
			//tokn更新
			b, _ := json.Marshal(gin.H{
				"type":    "uptoken",
				"context": token,
			})
			conn.WriteMessage(websocket.TextMessage, b)
		}
		//type类型验证
		if claim.Type == "close" {
			//主动关闭
			removeClientIn(claim.Email, conn)
			return
		}
		//连接句柄加入map中
		addHandeltomap(claim.Email, conn)
		//剥离信息
		var msg = gin.H{
			"context":  claim.Context,
			"name":     claim.Name,
			"email":    claim.Email,
			"sendtime": claim.Sendtime,
			"avatar":   claim.Avatar,
		}

		//数据全体广播
		sendMsgtoAll(msg)

		//数据存储在redis中 数据结构为list,存储的数据为json数据-->方便快速存取
		b, _ := json.Marshal(msg)
		ic := model.Redis.LPush(model.Ctx, myutils.Today, b)
		if ic.Err() != nil {
			b, _ := json.Marshal(gin.H{
				"type":    "error",
				"context": "信息存储失败",
			})
			conn.WriteMessage(websocket.TextMessage, b)
			continue
		}

	}
}

func removeClientIn(user string, conn *websocket.Conn) {
	//数据结构为list数组： 一个账号可以有多个连接句柄
	LoginList, ok := LoginMap.Load(user)
	if !ok {
		//说明还没有在用户列表里面，不理
		return
	} else {
		//一个可以以后防断言的方法
		loginlist := LoginList.(*list.List)
		//长度判断
		if loginlist.Len() <= 1 {
			//直接移除
			LoginMap.Delete(user)
		} else {
			//说明是长字段，移除掉list里面的相关字段 list里面放的是连接句柄，用指针来做判断
			for e := loginlist.Front(); e != nil; e = e.Next() {
				if e.Value.(*websocket.Conn) == conn {
					loginlist.Remove(e)
					break
				}
			}
			//重新设置map-->不是删除
			LoginMap.Store(user, loginlist)
		}
	}
}

//return token,error
func loginortoken(data *model.Chathandle) (string, error) {
	//判断有无token，没的话生成 ；有的话比对
	if data.Auth != "" {
		mc, code := middleware.CheckToken(data.Auth)
		if code != global.SUCCESS {
			return "", errors.New("token is error")
		} else {
			if mc.Username == data.Name {
				return data.Auth, nil
			} //token和用户名不一样情况：验证用户名时候存在冲突，生成新token
		}
	}
	//look at user  ::头像不需要验证,因为头像地址会变化
	d := model.DB.Where("username=? and email= ? ", data.Name, data.Email).First(&model.User{})
	if errors.Is(d.Error, gorm.ErrRecordNotFound) {
		//not found，create ones,and create a token
		var mo = model.User{
			Username: data.Name,
			Email:    data.Email,
			Avatar:   data.Avatar,
		}
		d := model.DB.Create(&mo)
		if errors.Is(d.Error, gorm.ErrRegistered) {
			//Registered
			return "", errors.New("registed email")
		} else if d.Error != nil {
			//Database Error
			return "", errors.New("database is error")
		}

	} else if d.Error != nil {
		return "", errors.New("database is error")
	}
	//use Email as Username to checking token
	token, code := middleware.CreateToken(data.Email, 0)
	if code != global.SUCCESS {
		return "", errors.New("create token error")
	}
	return token, nil
}

//添加数据到map
func addHandeltomap(user string, conn *websocket.Conn) {
	//先找有没有
	LoginList, ok := LoginMap.Load(user)
	if !ok {
		//加入一个新连接句柄 list.List形式
		loginlist := list.New()
		loginlist.PushBack(conn)
		//覆盖添加sync.map
		LoginMap.Store(user, loginlist)
		return
	}
	var loginlist = LoginList.(*list.List)
	var flag = true
	//重复这样会造成重复的句柄添加,应该句柄判断
	for e := loginlist.Front(); e != nil; e = e.Next() {
		if e.Value.(*websocket.Conn) == conn {
			flag = false
		}
	}
	if flag {
		loginlist.PushBack(conn)
		LoginMap.Store(user, loginlist)
	}
}

func sendMsgtoAll(msg gin.H) {
	b, _ := json.Marshal(msg)
	//遍历发送 ==Map的遍历函数Range
	LoginMap.Range(func(key, value interface{}) bool {
		var l = value.(*list.List)
		for e := l.Front(); e != nil; e = e.Next() {
			var conn = e.Value.(*websocket.Conn)
			conn.WriteMessage(websocket.TextMessage, b)
		}
		return true
	})
}
